package edu.rit.drh4818.volume;

import java.util.Collections;

import javax.media.j3d.*;
import javax.vecmath.*;


/**
 * This Branch Group renders a series of quads, and applies a texture map
 *  to them based on the data from the volume class.
 * 
 * The axis and direction are determined by calculating where the eye would be located,
 *  and determining the front face.
 * 
 * @author Darren Headrick
 *
 */
public class VolumeRenderer extends BranchGroup implements DirectionConstants 
{	
	/**
	 * Current direction and axis, based on eye's location.
	 */
	private int curAxis, curDir;

	/**
	 * Switch to enable the textures for only the current axis/dir.
	 */
	private Switch axisSwitch;
	
	/**
	 * Appearance attributes.
	 */
	private TextureAttributes texAttr = new TextureAttributes();
	private TransparencyAttributes t = new TransparencyAttributes();
	private PolygonAttributes p = new PolygonAttributes();
	private Material m = new Material();
	
	/**
	 * View.
	 */
	private View view;
	
	/**
	 * Volume.
	 */
	private Volume volume;
	
	/**
	 * Initialize the renderer.
	 * @param vw View to get eye pt location from.
	 * @param volume Volume to render.
	 */
	public VolumeRenderer(View vw, Volume volume) 
	{
		view = vw;
		this.volume = volume;

		// set up axis switch
		axisSwitch = new Switch();
		axisSwitch.setCapability(Switch.ALLOW_SWITCH_READ);
		axisSwitch.setCapability(Switch.ALLOW_SWITCH_WRITE);
		axisSwitch.setCapability(Group.ALLOW_CHILDREN_READ);
		axisSwitch.setCapability(Group.ALLOW_CHILDREN_WRITE);
		for( int x = 0; x < 6; x++ )
		{
			OrderedGroup og = new OrderedGroup();
			og.setCapability(Group.ALLOW_CHILDREN_READ);
			og.setCapability(Group.ALLOW_CHILDREN_WRITE);
			og.setCapability(Group.ALLOW_CHILDREN_EXTEND);
			axisSwitch.addChild( og );
		}
		
		// set up material.
		t.setTransparencyMode(TransparencyAttributes.BLENDED);
		m.setLightingEnable(true);
		p.setCullFace(PolygonAttributes.CULL_NONE);

		// set up branch group
		setCapability(BranchGroup.ALLOW_LOCAL_TO_VWORLD_READ);
		addChild(axisSwitch);
		setAxis(Z_AXIS, FRONT);
	}

	/**
	 * Called to make changes to the renderer state
	 */
	@SuppressWarnings("unchecked")
	public void update() 
	{
		// clear all the axis'
		for( Object node : Collections.list(axisSwitch.getAllChildren() ).toArray() )
		{
			((Group)node).removeAllChildren();
		}
		
		// To make the memory constant.
		if( Settings.MEMORY ) System.gc();
		
		// load the texture
		loadTexture();
	}

	/**
	 * Called when the view position relative to the renderer changes.
	 * Calculate which direction is now in 'front' and change axis accordingly.
	 */
	public void eyePtChanged() 
	{
		Point3d eyePt = getEyePosition();

		if (eyePt != null) 
		{
			Vector3d eyeVec = new Vector3d();
			eyeVec.sub(eyePt, new Point3d());

			// select the axis with the greatest magnitude 
			int axis = X_AXIS;
			double value = eyeVec.x;
			if (Math.abs(eyeVec.y) > Math.abs( value ) ) 
			{
				axis = Y_AXIS;
				value = eyeVec.y;
			}
			if (Math.abs(eyeVec.z) > Math.abs( value ) ) 
			{
				axis = Z_AXIS;
				value = eyeVec.z;
			}

			int dir = ( value > 0 ) ? FRONT : BACK;

			if ((axis != curAxis) || (dir != curDir)) 
			{
				setAxis(axis, dir);
			}
		}
	}
	
	/** 
	 * Get the eyes position.
	 * @return Location of eye, in local coordinate space.
	 */
	private Point3d getEyePosition() 
	{
		Point3d viewPosition = new Point3d();
		
		if (!isLive()) 
		{
			System.out.println("called getEyePosition() with non-live node");
			return null;
		}

		//  get viewplatforms's location in virutal world
		Canvas3D canvas = (Canvas3D) view.getCanvas3D(0);
		canvas.getCenterEyeInImagePlate(viewPosition);
		Transform3D t = new Transform3D();
		canvas.getImagePlateToVworld(t);
		t.transform(viewPosition);

		// get parent transform
		Transform3D parentInv = new Transform3D();
		getLocalToVworld(parentInv);
		parentInv.invert();

		// transform the eye position into the parent's coordinate system
		parentInv.transform(viewPosition);
		return viewPosition;
	}
	
	/**
	 * Load the texture along current axis.
	 */
	private void loadTexture() 
	{
		OrderedGroup frontGroup = (OrderedGroup) axisSwitch.getChild(getIndex(curAxis,FRONT));
		OrderedGroup backGroup = (OrderedGroup) axisSwitch.getChild(getIndex(curAxis,BACK));
		double[] qCoords = new double[12];
		
		switch (curAxis)
		{
			case X_AXIS:
				qCoords[1] = qCoords[2] = qCoords[5] = qCoords[10] = volume.minCoord;
				qCoords[4] = qCoords[7] = qCoords[8] = qCoords[11] = volume.maxCoord;
				break;
			case Y_AXIS:
				qCoords[0] = qCoords[2] = qCoords[3] = qCoords[11] = volume.minCoord;
				qCoords[5] = qCoords[6] = qCoords[8] = qCoords[9] = volume.maxCoord;
				break;
			case Z_AXIS:
				qCoords[0] = qCoords[1] = qCoords[4] = qCoords[9] = volume.minCoord;
				qCoords[3] = qCoords[6] = qCoords[7] = qCoords[10] = volume.maxCoord;
				break;
		}

		for (int i = 0; i < volume.dim; i++) 
		{
			qCoords[curAxis] = qCoords[3+curAxis] = qCoords[6+curAxis] = 
					qCoords[9+curAxis] = (float)i / volume.dim;
			
			Appearance a = new Appearance();
			a.setMaterial(m);
			a.setTransparencyAttributes(t);
			a.setTextureAttributes(texAttr);
			a.setTexture(volume.texture);
			a.setTexCoordGeneration(volume.tg);
			a.setPolygonAttributes(p);

			QuadArray quadArray = new QuadArray(4, GeometryArray.COORDINATES);
			quadArray.setCoordinates(0, qCoords);

			// set up front shape
			Shape3D frontShape = new Shape3D(quadArray, a);
			BranchGroup frontShapeGroup = new BranchGroup();
			frontShapeGroup.setCapability(BranchGroup.ALLOW_DETACH);
			frontShapeGroup.addChild(frontShape);
			frontGroup.addChild(frontShapeGroup);

			// setup back shape
			Shape3D backShape = new Shape3D(quadArray, a);
			BranchGroup backShapeGroup = new BranchGroup();
			backShapeGroup.setCapability(BranchGroup.ALLOW_DETACH);
			backShapeGroup.addChild(backShape);
			backGroup.insertChild(backShapeGroup, 0);
		}
	}
	
	/**
	 * Calculate the index.
	 * @param axis Axis
	 * @param direction Direction
	 * @return 
	 */
	private int getIndex( int axis, int direction )
	{
		return ( axis * 2 + direction );
	}
	
	/**
	 * Set the axis.
	 * @param axis New axis.
	 * @param dir New direction.
	 */
	private void setAxis(int axis, int dir) {
		curAxis = axis;
		curDir = dir;
		loadTexture();
		axisSwitch.setWhichChild(getIndex(curAxis,curDir));
	}
}
